package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.CouponCreateDTO;
import com.cloudfun.campusshare.common.model.dto.CouponSearchDTO;
import com.cloudfun.campusshare.common.model.dto.PromotionSimpleDTO;
import com.cloudfun.campusshare.common.model.vo.CouponListVO;
import com.cloudfun.campusshare.common.model.vo.CouponSimpleVO;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.entity.CouponEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.repo.CouponPromotionRepo;
import com.cloudfun.campusshare.repo.CouponRepo;
import com.cloudfun.campusshare.service.CouponService;
import com.cloudfun.campusshare.service.CreatorPersistenceService;
import com.cloudfun.campusshare.service.UserRelService;
import com.cloudfun.campusshare.util.AmountUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by CodeGenerator on 2020/06/05
 */
@Slf4j
@Service
public class CouponServiceImpl extends CreatorPersistenceService implements CouponService {

    @Autowired
    private CouponRepo couponRepo;

    @Autowired
    private UserRelService userRelService;

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private CouponPromotionRepo couponPromotionRepo;

    @Override
    protected Class<?> getEntityClass() {
        return CouponEntity.class;
    }

    @Override
    public void create(CouponCreateDTO couponCreateDTO) {
        CouponEntity couponEntity = new CouponEntity();
        BeanUtil.copyProperties(couponCreateDTO, couponEntity);
        UserEntity userMerchant = userRelService.getUserMerchant(AuthenticationHolder.getUserId());
        couponEntity.setAmount(couponEntity.getAmount() * 100);
        couponEntity.setMerchantId(userMerchant.getId());
        couponRepo.save(couponEntity);
    }

    @Override
    public void modify(CouponCreateDTO couponCreateDTO) {

    }

    @Override
    public List<CouponSimpleVO> simpleList(CouponSearchDTO searchDTO) {
        Specification<CouponEntity> specification = this.getSpec(searchDTO);
        List<CouponEntity> couponEntities = couponRepo.findAll(specification);
        return couponEntities.stream()
                .map(entity -> {
                    CouponSimpleVO simpleVO = new CouponSimpleVO();
                    BeanUtil.copyProperties(entity, simpleVO);
                    return simpleVO;
                }).collect(Collectors.toList());
    }

    @Override
    public PageResult<CouponListVO> search(CouponSearchDTO searchDTO, Pageable pageable) {
        Specification<CouponEntity> specification = this.getSpec(searchDTO);
        Page<CouponEntity> couponEntityPage = couponRepo.findAll(specification, pageable);
        Set<String> ids = couponEntityPage.getContent().stream().map(CouponEntity::getId).collect(Collectors.toSet());
        Map<String, List<PromotionSimpleDTO>> couponAndPromotionsMap = this.getCouponAndPromotionsMap(ids);
        List<CouponListVO> content = couponEntityPage.getContent().stream()
                .map(entity -> {
                    CouponListVO couponListVO = new CouponListVO();
                    BeanUtil.copyProperties(entity, couponListVO);
                    couponListVO.setPromotions(couponAndPromotionsMap.get(entity.getId()));
                    couponListVO.setAmount(AmountUtil.amountRoundStr(entity.getAmount(), 2));
                    return couponListVO;
                }).collect(Collectors.toList());
        return new PageResult<>(couponEntityPage.getTotalElements(), content);
    }

    public Specification<CouponEntity> getSpec(CouponSearchDTO searchDTO) {
        Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
        return (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            if (AuthenticationHolder.isAdmin()) {
                if (!StringUtils.isBlank(searchDTO.getMerchantId())) {
                    list.add(cb.equal(root.get("merchantId"), searchDTO.getMerchantId()));
                }
            } else {
                if (viewAllData) {
                    String userId = AuthenticationHolder.getUserId();
                    UserEntity merchant = userRelService.getUserMerchant(userId);
                    list.add(cb.equal(root.get("merchantId"), merchant.getId()));
                } else {
                    Set<String> allSub = userRelService.getDescendantIds(AuthenticationHolder.getUserId());
                    allSub.add(AuthenticationHolder.getUserId());
                    list.add(root.get("creatorId").in(allSub));
                }
            }
            if (StringUtils.isNotBlank(searchDTO.getKeyword())) {
                List<Predicate> like = new ArrayList<>();
                Predicate nameLikePredicate = cb.like(root.get("name"), "%" + searchDTO.getKeyword() + "%");
                like.add(nameLikePredicate);
                Predicate[] predicates = new Predicate[like.size()];
                list.add(cb.or(like.toArray(predicates)));
            }

            Predicate[] predicates = new Predicate[list.size()];
            list.toArray(predicates);
            return cb.and(predicates);
        };
    }

    @Override
    public void delete(String id) {
        // 有关联活动时 不允许删除
        Long count = couponPromotionRepo.countAllByCouponId(id);
        if (null != count && count > NumberUtils.LONG_ZERO) {
            throw new BusinessException(Code.ERROR, "代金券已被某项活动关联，请调整后重试");
        }
        couponRepo.deleteById(id);
    }

    @Override
    @Transactional
    public Map<Code, Integer> delete(List<String> ids) {
        List<CouponEntity> entities = couponRepo.findAllByIdIn(ids);
        return this.doDelete(entities);
    }

    @Override
    @Transactional
    public Map<Code, Integer> delete(CouponSearchDTO searchDTO) {
        Specification<CouponEntity> spec = this.getSpec(searchDTO);
        List<CouponEntity> applicationEntities = couponRepo.findAll(spec);
        return this.doDelete(applicationEntities);
    }

    @Override
    @Transactional
    public Map<Code, Integer> deleteAll() {
        CouponSearchDTO searchDTO = new CouponSearchDTO();
        searchDTO.setMerchantId("-1");
        return this.delete(searchDTO);
    }

    /**
     * 获取代金券-活动映射
     *
     * @param couponIds
     * @return
     */
    private Map<String, List<PromotionSimpleDTO>> getCouponAndPromotionsMap(Set<String> couponIds) {
        if (CollectionUtils.isEmpty(couponIds)) {
            return new HashMap<>();
        }
        Query query = entityManager.createNativeQuery("select cp.coupon_id,p.id,p.name from" +
                " coupon_promotion cp join promotion p on cp.promotion_id=p.id where cp.coupon_id in (:couponIds)");
        query.setParameter("couponIds", couponIds);
        List<Object[]> resultList = query.getResultList();
        return resultList.stream().map(tuple -> {
            String couponId = (String) tuple[0];
            String promotionId = (String) tuple[1];
            String promotionName = (String) tuple[2];
            return new PromotionSimpleDTO(promotionId, promotionName, couponId);
        }).collect(Collectors.groupingBy(PromotionSimpleDTO::getCouponId));
    }

    private Map<Code, Integer> doDelete(List<CouponEntity> entities) {
        // 有关联活动时 不允许删除
        Set<String> ids = entities.stream().map(CouponEntity::getId).collect(Collectors.toSet());
        Map<String, List<PromotionSimpleDTO>> couponAndPromotionsMap = this.getCouponAndPromotionsMap(ids);
        List<CouponEntity> toBeDeleted = entities.stream().filter(entity ->
                !couponAndPromotionsMap.containsKey(entity.getId()) || CollectionUtils.isEmpty(couponAndPromotionsMap.get(entity.getId())))
                .collect(Collectors.toList());
        couponRepo.deleteAll(toBeDeleted);
        Map<Code, Integer> res = new HashMap<>();
        res.put(Code.SUCCESS, toBeDeleted.size());
        res.put(Code.ERROR, entities.size() - toBeDeleted.size());
        return res;
    }
}